﻿using System;
using System.Collections.Generic;
using static LightCAD.MathLib.Constants;
using LightCAD.Three.OpenGL;
using LightCAD.MathLib;

namespace LightCAD.Three
{
    public class WebGLBackground
    {
        public static readonly Color _rgb = new Color { R = 0, G = 0, B = 0 };

        public readonly Color clearColor;
        public float clearAlpha;
        public Mesh planeMesh;
        public Mesh boxMesh;
        public object currentBackground;
        public int currentBackgroundVersion = 0;
        public int? currentTonemapping = null;
        private WebGLRenderer renderer;
        private WebGLCubeMaps cubemaps;
        private WebGLCubeUVMaps cubeuvmaps;
        private WebGLObjects objects;
        private WebGLState state;
        private bool premultipliedAlpha;
        public WebGLBackground(WebGLRenderer renderer, WebGLCubeMaps cubemaps, WebGLCubeUVMaps cubeuvmaps, WebGLState state, WebGLObjects objects, bool alpha, bool premultipliedAlpha)
        {
            this.clearColor = new Color(0x000000);
            this.clearAlpha = alpha ? 0 : 1;
            this.planeMesh = null;
            this.boxMesh = null;
            this.currentBackground = null;
            this.currentBackgroundVersion = 0;
            this.currentTonemapping = null;
            this.renderer = renderer;
            this.cubemaps = cubemaps;
            this.cubeuvmaps = cubeuvmaps;
            this.objects = objects;
            this.state = state;
            this.premultipliedAlpha = premultipliedAlpha;
        }
        public void render(WebGLRenderList renderList, Object3D scene)
        {
            var forceClear = false;
            var background = scene is Scene ? (scene as Scene).background : null;

            if (background != null && background is Texture)
            {
                var usePMREM = (scene as Scene).backgroundBlurriness > 0; // use PMREM if the user wants to blur the background
                background = (usePMREM ? cubeuvmaps : cubemaps).get(background as Texture);
            }

            // Ignore background in AR
            // TODO: Reconsider this.

            //var xr = renderer.xr;
            //var session = xr.getSession != null ? xr.getSession.Invoke() : null;

            //if (session != null && session.environmentBlendMode == "additive")
            //{
            //    background = null;
            //}

            if (background == null)
            {
                setClear(clearColor, clearAlpha);
            }
            else if (background != null && background is Color)
            {

                setClear(background as Color, 1);
                forceClear = true;

            }

            if (renderer.autoClear || forceClear)
            {

                renderer.clear(renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil);

            }

            if (background != null && (background is CubeTexture || (background as Texture)?.mapping == CubeUVReflectionMapping))
            {
                if (boxMesh == null)
                {
                    var boxMat = new ShaderMaterial
                    {
                        name = "BackgroundCubeMaterial",
                        uniforms = UniformsUtils.cloneUniforms(ShaderLib.backgroundCube.uniforms),
                        vertexShader = ShaderLib.backgroundCube.vertexShader,
                        fragmentShader = ShaderLib.backgroundCube.fragmentShader,
                        side = BackSide,
                        depthTest = false,
                        depthWrite = false,
                        fog = false
                    };
                    boxMesh = new Mesh(new BoxGeometry(1, 1, 1), boxMat);

                    boxMesh.geometry.deleteAttribute("normal");
                    boxMesh.geometry.deleteAttribute("uv");
                    boxMesh.onBeforeRenderAction = new Action<WebGLRenderer, Object3D, Camera, object, object, object>((_renderer, _scene, _camera, obj1, obj2, obj3) =>
                    {
                        boxMesh.matrixWorld.CopyPosition(_camera.matrixWorld);
                    });
                    // add "envMap" material property so the renderer can evaluate it like for built-in materials
                    //boxMat.envMapFunc = () =>
                    //   (Texture)boxMat.uniforms["envMap"].value;
                    objects.update(boxMesh);
                }
                var shaderMaterial = boxMesh.material as ShaderMaterial;
                shaderMaterial.uniforms["envMap"].value = background;
                shaderMaterial.uniforms["flipEnvMap"].value = (background is CubeTexture && (background as CubeTexture).isRenderTargetTexture == false) ? -1 : 1;
                shaderMaterial.uniforms["backgroundBlurriness"].value = scene["backgroundBlurriness"];
                shaderMaterial.uniforms["backgroundIntensity"].value = scene["backgroundIntensity"];
                shaderMaterial.toneMapped = (background as Texture).encoding == sRGBEncoding ? false : true;
                shaderMaterial.envMap = (Texture)shaderMaterial.uniforms["envMap"].value;
                if (currentBackground != background ||
                    currentBackgroundVersion != (background as Texture).version ||
                    currentTonemapping != renderer.toneMapping)
                {

                    boxMesh.material.needsUpdate = true;

                    currentBackground = background;
                    currentBackgroundVersion = (background as Texture).version;
                    currentTonemapping = renderer.toneMapping;

                }

                boxMesh.layers.enableAll();

                // push to the pre-sorted opaque render list
                renderList.unshift(boxMesh, boxMesh.geometry, boxMesh.material, 0, 0, null);

            }
            else if (background != null && background is Texture)
            {
                var bakcgroudTexture = background as Texture;
                if (planeMesh == null)
                {
                    var planeMat = new ShaderMaterial
                    {
                        name = "BackgroundMaterial",
                        uniforms = UniformsUtils.cloneUniforms(ShaderLib.background.uniforms),
                        vertexShader = ShaderLib.background.vertexShader,
                        fragmentShader = ShaderLib.background.fragmentShader,
                        side = FrontSide,
                        depthTest = false,
                        depthWrite = false,
                        fog = false
                    };
                    planeMesh = new Mesh(new PlaneGeometry(2, 2), planeMat);

                    planeMesh.geometry.deleteAttribute("normal");

                    // add "map" material property so the renderer can evaluate it like for built-in materials

                    //planeMat.mapFunc =()=> (Texture)planeMat.uniforms["t2D"].value;

                    objects.update(planeMesh);

                }
                var planeMaterial = planeMesh.material as ShaderMaterial;
                planeMaterial.uniforms["t2D"].value = background;
                planeMaterial.uniforms["backgroundIntensity"].value = scene["backgroundIntensity"];
                planeMaterial.toneMapped = ((background as Texture).encoding == sRGBEncoding) ? false : true;
                planeMaterial.map = (Texture)background;
                if (bakcgroudTexture.matrixAutoUpdate)
                {

                    bakcgroudTexture.updateMatrix();

                }

                (planeMaterial.uniforms["uvTransform"].value as Matrix3).Copy(bakcgroudTexture.matrix);

                if (currentBackground != background ||
                    currentBackgroundVersion != bakcgroudTexture.version ||
                    currentTonemapping != renderer.toneMapping)
                {

                    planeMesh.material.needsUpdate = true;

                    currentBackground = background;
                    currentBackgroundVersion = bakcgroudTexture.version;
                    currentTonemapping = renderer.toneMapping;

                }

                planeMesh.layers.enableAll();

                // push to the pre-sorted opaque render list
                renderList.unshift(planeMesh, planeMesh.geometry, planeMesh.material, 0, 0, null);
            }

        }
        void setClear(Color color, float alpha = 1)
        {

            color.GetRGB(_rgb, UniformsUtils.getUnlitUniformColorSpace(renderer));

            state.buffers.color.setClear((float)_rgb.R, (float)_rgb.G, (float)_rgb.B, (float)alpha, premultipliedAlpha);

        }
        public Color getClearColor()
        {
            return clearColor;
        }
        public void setClearColor(object color, float alpha = 1)
        {

            clearColor.Set(color);
            clearAlpha = alpha;
            setClear(clearColor, clearAlpha);

        }
        public float getClearAlpha()
        {

            return clearAlpha;

        }
        public void setClearAlpha(float alpha)
        {

            clearAlpha = alpha;
            setClear(clearColor, clearAlpha);

        }
    }
}
